{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "f4019ed7",
   "metadata": {},
   "source": [
    "## 1. 图的节点工作过程\n",
    "\n",
    "节点之间通过消息传递进行通信。\n",
    "\n",
    "一个节点完成工作以后，会向邻居节点发送消息。收到消息的节点，会进行相应的处理。节点的工作过程在一个超步中完成以下步骤：\n",
    "\n",
    "1. 初始状态为所有节点都处于`inactive`状态。\n",
    "2. 每个节点在每个超步中，根据自身状态和收到的消息，计算出新的状态。\n",
    "3. 节点在`active`状态下，完成自己的工作，之后会向邻居节点发送消息。\n",
    "4. 超步结束，如果没有新的消息产生，那么整个图的计算就结束了。节点会举手进入到`vote to halt`状态，等待其他节点也举手，进入`halted`状态，表示整个图计算结束。\n",
    "5. 系统将举手的节点，标记为休息状态。\n",
    "6. 节点最终回到初始的`inactive`状态。\n",
    "7. 最终，当所有的节点都进入`halted`状态时，整个图计算结束。\n",
    "\n",
    "注意：\n",
    "1. 在使用图之前，需要对图进行编译（验证图结构、构建 CompiledGraph 对象、验证 CompiledGraph 对象）。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28e8aca6",
   "metadata": {},
   "source": [
    "## 2. 状态\n",
    "\n",
    "状态包含两个关键部分，一个是模式 Schema ， 一个是规约函数 reducer。Schema 定义了状态的数据结构，而 reducer 则定义了如何根据输入更新状态。\n",
    "\n",
    "Schema 定义了数据的结构和类型，可以用 TypedDict 和 Pydantic 来定义。\n",
    "\n",
    "```python\n",
    "from typing import Annotated, Sequence\n",
    "from typing_extensions import TypedDict\n",
    "import operator\n",
    "\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "\n",
    "# 定义状态 Schema 使用 Pydantic\n",
    "class AgentState(BaseModel):\n",
    "    messages: Annotated[Sequence[str], operator.add] = Field(default_factory=list)\n",
    "    user_input: str = \"\"\n",
    "    tool_calls: list = Field(default_factory=list)\n",
    "    final_response: str = \"\"\n",
    "\n",
    "\n",
    "# 定义状态 Schema 使用 TypedDict\n",
    "class GraphState(TypedDict):\n",
    "    messages: Annotated[Sequence[str], operator.add]\n",
    "    user_input: str\n",
    "    tool_calls: list\n",
    "    final_response: str\n",
    "```\n",
    "\n",
    "LangGraph 的图支持三种类型的状态模式，分别是：\n",
    "\n",
    "1. 共享模式：共享模式的状态会被所有节点共享。共享模式的状态 Schema 必须是可哈希的。\n",
    "2. 私有模式：私有模式的状态不会被其他节点共享。私有模式的状态 Schema 可以是可变对象。\n",
    "3. 内部模式：内部模式的状态不会被其他节点共享。内部模式的状态 Schema 必须是可哈希的。\n",
    "\n",
    "规约函数 reducer 指定如何将节点的更新应用到状态上。每个状态键都有独立的规约函数，如果不指定，默认是覆盖更新。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68263d00",
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "source": [
    "## 3. 节点\n",
    "\n",
    "节点支持同步和异步，本质上是 Python 函数。\n",
    "\n",
    "但是第一个位置参数必须是状态`state`，第二个位置参数是可选的配置`config`。\n",
    "\n",
    "节点会被自动转换为 RunableLamaba 对象，支持批处理和异步操作。\n",
    "\n",
    "节点返回值会作为状态更新， 只需包含要更新的字段。\n",
    "\n",
    "```Python\n",
    "from typing import Annotated, Sequence\n",
    "from typing_extensions import TypedDict\n",
    "import operator\n",
    "\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "\n",
    "\n",
    "# 定义状态 Schema 使用 TypedDict\n",
    "class GraphState(TypedDict):\n",
    "    messages: Annotated[Sequence[str], operator.add]\n",
    "    user_input: str\n",
    "    tool_calls: list\n",
    "    final_response: str\n",
    "\n",
    "\n",
    "# 节点函数\n",
    "def handle_user_input(state: GraphState) -> dict:\n",
    "    return {\"user_input\": state[\"messages\"][-1] if state[\"messages\"] else \"\"}\n",
    "\n",
    "\n",
    "def generate_response(state: GraphState) -> dict:\n",
    "    response = f\"AI 回复: 收到你的消息: {state['user_input']}\"\n",
    "    return {\n",
    "        \"messages\": [response],\n",
    "        \"final_response\": response,\n",
    "    }\n",
    "\n",
    "\n",
    "# 构建图\n",
    "builder = StateGraph(GraphState)\n",
    "\n",
    "builder.add_node(\"input\", handle_user_input)\n",
    "builder.add_node(\"llm\", generate_response)\n",
    "\n",
    "builder.add_edge(START, \"input\")\n",
    "builder.add_edge(\"input\", \"llm\")\n",
    "builder.add_edge(\"llm\", END)\n",
    "\n",
    "# 编译图\n",
    "graph = builder.compile()\n",
    "\n",
    "# 运行\n",
    "result = graph.invoke({\n",
    "    \"messages\": [\"Hello from TypedDict!\"],\n",
    "    \"user_input\": \"\",\n",
    "    \"tool_calls\": [],\n",
    "    \"final_response\": \"\"\n",
    "})\n",
    "\n",
    "print(result[\"final_response\"])\n",
    "# 输出: AI 回复: 收到你的消息: Hello from TypedDict!\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4dbfa74",
   "metadata": {},
   "source": [
    "## 4. 边\n",
    "\n",
    "边定义了数据的连接关系，是数据流动的通道。边有四种类型：\n",
    "\n",
    "1. 普通边\n",
    "2. 条件边\n",
    "3. 入口边\n",
    "4. 条件入口边\n",
    "\n",
    "### 4.1  普通边\n",
    "\n",
    "普通边是最常见的边，它表示两个节点之间的直接连接关系。\n",
    "\n",
    "```python\n",
    "builder.add_edge(\"node1\", \"node2\")\n",
    "```\n",
    "\n",
    "### 4.2 条件边\n",
    "\n",
    "条件边表示节点之间的连接需要满足特定的条件，比如下面代码中，只有当 `node1` 的值大于 `0` 时，才会建立 `node1` 和 `node2` 之间的连接。\n",
    "\n",
    "```python\n",
    "def route_condition(state: State):\n",
    "    if state[\"score\"] <= 60:\n",
    "        return \"node4\"\n",
    "    else:\n",
    "        return \"node5\"\n",
    "\n",
    "# 直接使用返回值作为下一个节点\n",
    "builder.add_edge(\"node3\", condition=route_condition)\n",
    "\n",
    "# 使用映射字典定义下一个节点\n",
    "builder.add_edge(\n",
    "    \"node3\", \n",
    "    condition=route_condition, \n",
    "    { True: \"node4\", False: \"node5\" }\n",
    ")\n",
    "```\n",
    "\n",
    "### 4.3 入口边\n",
    "```python\n",
    "builder.add_entry(\"node1\")\n",
    "```\n",
    "\n",
    "### 4.4 条件入口边\n",
    "```python\n",
    "def route_condition(state: State):\n",
    "    if state.get(\"is_login\"):\n",
    "        return \"node1\"\n",
    "    return \"node2\"\n",
    "\n",
    "builder.add_conditional_entry(START, route_condition)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c39070a",
   "metadata": {},
   "source": [
    "## 5. 其他重要概念\n",
    "\n",
    "1. 子图：子图是一种封装机制，允许将一个完整的图作为节点嵌入到另一个图中。这种机制是 MultiAgent 的核心。\n",
    "2. 检查点：checkpoint 是图状态的快照，由 StateSnapshot 对象表示。\n",
    "3. 会话： session 是用户与图之间的对话。在 Python 中用线程表示。每个 session 有唯一的会话 ID。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "MLOps",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
